Fix various multicluster issues#1480
Merged
andrewstucki merged 4 commits intomainfrom Apr 24, 2026
Merged
Conversation
Contributor
Author
|
See the NOTE: this also exists in the current Redpanda controller and will need to be partially backported. -- I'll open up individual "backport" PRs for this fix to our current target branches. |
This was referenced Apr 24, 2026
hidalgopl
approved these changes
Apr 24, 2026
RafalKorepta
approved these changes
Apr 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A batch of fixes surfaced during end-to-end stretch-cluster partition testing (3-cluster demo across EKS/AKS/GKE over Tailscale + Cilium ClusterMesh), plus tooling improvements to
rpk k8s multiclusterthat remove the chicken-and-egg bootstrap cycle when leveraging publicly accessible load balancers and fix the TLS-SAN health check.Bug fixes
Hot reconcile loop on healthy stretch clusters
Two root causes produced a steady stream of
.statuswrites at the condition-heartbeat cadence:features.Featuresproduced a non-deterministicInUseFeaturesordering on each call, flippingLicenseValid's stored value on every reconcile. Fixed bysort.Strings(inUseFeatures)in bothoperator/internal/controller/redpanda/multicluster_controller.goandoperator/internal/controller/redpanda/redpanda_controller.go.operator/statuses.yamland regeneratedoperator/internal/statuses/zz_generated_status.go. The forced-dirty write remains solastUpdateTimeon the condition stays meaningful.NOTE: this also exists in the current Redpanda controller and will need to be partially backported.
PodEndpointsdropped during cross-cluster owner rebuildIn flat-network mode,
StretchClusterOwnershipResolver.ResolveOwnerReferencerebuilds the owner wrapper when the owner UID comes from a different k8s cluster than the one being reconciled. The rebuild usedNewStretchClusterWithPools, which carries forwardNodePoolsbut notPodEndpoints. Result: eachSyncAlliteration that targeted a peer cluster would render flat-mode per-pod Endpoints with an empty IP list, and the Syncer would GC existing cross-clusterEndpoints/EndpointSlicesevery reconcile cycle.Fixed by preserving
PodEndpointson the new owner inoperator/internal/lifecycle/stretch_cluster_ownership.go.Observability
Trace logging added along the flat-mode per-pod Endpoints path so the next time this regresses we can see it in the logs rather than correlating timestamps with
kubectl get endpoints:operator/internal/lifecycle/client.go:FetchExistingAndDesiredPoolsandfetchExistingPools.operator/internal/lifecycle/pool.go:PodEndpoints(ctx)— addedctxparameter, logs per-cluster pod/ready/no-IP counts.operator/multicluster/endpoints.go: info-level log when rendering nil on emptypodEndpoints(the smoking-gun signal for the GC-loop bug), plus trace logs for rendered counts and per-service skips.operator/multicluster/render_state.go:RenderState.ctxfield +WithContext/Contextmethods to plumb the reconcile-scoped logger down to render helpers.operator/internal/lifecycle/stretch_cluster_simple_resources.go: threads reconcilectxinto the render state.rpk k8s multicluster status— TLS SAN check correctnessoperator/cmd/rpk-k8s/k8s/multicluster/checks/cluster_tls_san.gorewritten. The previous check substring-matched the cluster's logical name againstcert.DNSNames(e.g.,"two"against DNS SANs), so:cert.IPAddresseswasn't consulted.Now resolves the expected peer address from the Deployment's
--peer=<self>://<addr>:9443flag and callsx509.Certificate.VerifyHostname(addr)— the same routine Go's TLS stack uses during a real peer dial. Handles DNS wildcards, IP literals, and case normalization uniformly. Error messages include bothDNSNamesandIPAddressesso the cause is obvious without pulling the cert.rpk k8s multicluster bootstrap --loadbalancerNew flag resolves the deploy/redeploy chicken-and-egg: previously, to get peer addresses into each cluster's cert SANs and
--peerflags when leveraging an external load balancer, you'd install the operator, wait for the chart'sLoadBalancerService to provision, read the addresses, and redeploy with those values baked into helm values.Now
bootstrap --loadbalancerprovisions a standalone peer-onlyLoadBalancerService per cluster (name<fullname>-multicluster-peer, distinct from the chart's Service, labelledoperator.redpanda.com/bootstrap-managed=truefor discovery), waits for the provider to publish an address, and signs each cert with that address in the SANs. On completion it prints a ready-to-pastemulticluster.peersblock in helm-values shape.New files:
pkg/multicluster/bootstrap/loadbalancer.go:EnsurePeerLoadBalancer— idempotent, re-runs reuse the existing Service and re-read the address so repeated invocations don't thrash cloud LB resources.operator/cmd/rpk-k8s/k8s/multicluster/spinner.go: minimal TTY-aware spinner so the provisioning wait (often several minutes) has visible progress. Falls back to plain lines when stdout isn't a TTY.Precedence when both
--loadbalancerand--dns-overrideare specified:--dns-overridewins for the cert SAN, LB provisioning is skipped for that cluster. If someone needs "always provision, but cert SAN from override" it's a follow-up that splitsServiceAddressinto advertise-as vs cert-SAN fields.Chart:
multicluster.serviceaddition.We now have the ability to render out a service alongside each operator deployment that is configurable so that things like service meshes/MCS implementations can quickly figure out the discovery mechanisms for the operator. The service/MCS primitives are disabled by default, and if you want to have the operators communicate over a public network, the bootstrap process
--loadbalancerflag should likely be used instead, though you can also combine provisioning a loadbalancer service here with something like external dns annotations to give it a pre-determined well-known hostname.